<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/multi_dimensional_view.html">
<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
<link rel="import" href="/tracing/model/helpers/chrome_renderer_helper.html">

<script>
'use strict';

tr.exportTo('tr.e.chrome', function() {
  class CpuTime {
    /**
     * Returns two level map of rail stage to initiator type to set of bounds of
     * associated segments, intersected with |rangeOfInterest|.
     *
     * For each rail stage, we additionally have a key 'all_initiators' that
     * returns all the segment bounds associated with that rail stage across all
     * initiator types. For completeness, there is an additional rail stage
     * 'all_stages' that has all the segment bounds across all rail stages.
     *
     * If a segment is not contained within |rangeOfInterest| it is not
     * included.
     *
     * There is a unique segment bound for each segment in the map. For example,
     * assume
     * - |segmentA| is associated with both Click Response and Scroll Animation
     * - |bound1| is the interesting bound of |segmentA| in Response -> Click
     *   set.
     * - |bound2| is the interesting bound of |segmentA| in Animation -> Scroll
     *   set.
     * Then bound1 === bound2. These segment bounds can therefore be used as
     * keys in a map to represent the segment.
     *
     * Example return value (all bounds are intersected with |rangeOfInterest|):
     *
     * {
     *   'Animation': {
     *     'CSS': {Segment bounds for CSS Animation},
     *     'Video': {Segment bounds for Video Animation},
     *     ...
     *     'all_initiators': {All Animation segment bounds}
     *   },
     *   'Response': {
     *     'Click': {Segment bounds for Click Response},
     *     'Scroll': {Segment bounds for Scroll Response},
     *     ...
     *     'all_initiators': {All Response segment bounds}
     *   },
     *   ...
     *   'all_stages': {
     *     'all_initiators': {All segment bounds}
     *   }
     * }
     *
     * @param {!Array.<!tr.model.um.Segment>} segments
     * @param {!Array.<!tr.b.math.Range>} rangeOfInterest
     * @returns {!Map.<string, Map.<string, Set.<!tr.b.math.Range>>}
     */
    static getStageToInitiatorToSegmentBounds(segments, rangeOfInterest) {
      const stageToInitiatorToRanges = new Map();
      stageToInitiatorToRanges.set('all_stages',
          new Map([['all_initiators', new Set()]]));
      const allRanges =
          stageToInitiatorToRanges.get('all_stages').get('all_initiators');

      for (const segment of segments) {
        if (!rangeOfInterest.intersectsRangeInclusive(segment.range)) continue;
        const intersectingRange =
            rangeOfInterest.findIntersection(segment.range);
        allRanges.add(intersectingRange);

        for (const expectation of segment.expectations) {
          const stageTitle = expectation.stageTitle;
          if (!stageToInitiatorToRanges.has(stageTitle)) {
            stageToInitiatorToRanges.set(stageTitle,
                new Map([['all_initiators', new Set()]]));
          }

          const initiatorToRanges = stageToInitiatorToRanges.get(stageTitle);
          initiatorToRanges.get('all_initiators').add(intersectingRange);

          const initiatorType = expectation.initiatorType;
          if (initiatorType) {
            if (!initiatorToRanges.has(initiatorType)) {
              initiatorToRanges.set(initiatorType, new Set());
            }
            initiatorToRanges.get(initiatorType).add(intersectingRange);
          }
        }
      }
      return stageToInitiatorToRanges;
    }

    /**
     * Returns the root node of a MultiDimensionalView in TopDownTreeView for
     * cpu time.
     *
     * The returned tree view is three dimensional (processType, threadType, and
     * railStage + initiator). Rail stage and initiator are not separate
     * dimensions because they are not independent - there is no such thing as
     * CSS Response or Scroll Load.
     *
     * Each node in the tree view contains two values - cpuUsage and cpuTotal.
     *
     * See cpu_time_multidimensinoal_view.md for more details about the returned
     * multidimensional view.
     *
     * @param {!tr.Model} model
     * @param {!tr.b.math.Range} rangeOfInterest
     * @returns {!tr.b.MultiDimensionalViewNode}
     */
    static constructMultiDimensionalView(model, rangeOfInterest) {
      const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
          3 /* dimensions (process, thread and rail stage / initiator) */,
          2 /* valueCount (cpuUsage and cpuTotal) */);

      const stageToInitiatorToRanges =
          CpuTime.getStageToInitiatorToSegmentBounds(
              model.userModel.segments, rangeOfInterest);

      const allSegmentBoundsInRange =
          stageToInitiatorToRanges.get('all_stages').get('all_initiators');

      for (const [pid, process] of Object.entries(model.processes)) {
        const processType =
            tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);
        for (const [tid, thread] of Object.entries(process.threads)) {
          // Cache cpuTime for each segment bound.
          const rangeToCpuTime = new Map();
          for (const range of allSegmentBoundsInRange) {
            rangeToCpuTime.set(range, thread.getCpuTimeForRange(range));
          }

          for (const [stage, initiatorToRanges] of stageToInitiatorToRanges) {
            for (const [initiator, ranges] of initiatorToRanges) {
              const cpuTime = tr.b.math.Statistics.sum(ranges,
                  range => rangeToCpuTime.get(range));
              const duration = tr.b.math.Statistics.sum(ranges,
                  range => range.duration);
              const cpuTimePerSecond = cpuTime / duration;
              mdvBuilder.addPath(
                  [[processType], [thread.type], [stage, initiator]],
                  [cpuTimePerSecond, cpuTime],
                  tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
            }
          }
        }
      }

      return mdvBuilder.buildTopDownTreeView();
    }
  }

  return {
    CpuTime,
  };
});
</script>
